#include <charconv>
#include "Conversion.h"

ConvertSetting::ConvertSetting(bool insert, bool upper, size_t charPerLine, NewLineConvertMethod toHexNewlineMethod,
    NewLineConvertMethod toAsciiNewlineMethod, bool toAsciiSkipOutNewline, bool wholeTextCvt, NewLineType newline)
	:
    insertSpace(insert),
    uppercase(upper),
    charsPerLine(charPerLine),
    toHexNewlineCvtMethod(toHexNewlineMethod),
    toAsciiNewlineCvtMethod(toAsciiNewlineMethod),
    toAsciiSkipOutputNewline(toAsciiSkipOutNewline),
    wholeTextConvert(wholeTextCvt),
    newLineType(newline)
{}

bool Converter::HexToChar(uchar& c)
{
    if (c >= '0' && c <= '9') {
        c = c - '0';
        return true;
    } else if (c >= 'a' && c <= 'f') {
        c = c - 'a' + 0xa;
        return true;
    }
    return false;
}

static const char g_hexChars[] = "0123456789abcdef";
static const char g_hexUpperChars[] = "0123456789ABCDEF";
QByteArray Converter::CharToIntStr(uint64_t c, int radix, bool upperCase)
{
    if (radix == 16 && c <= 0xff) {
        const char* p = upperCase ? g_hexUpperChars : g_hexChars;
        QByteArray ret;
        ret.append(1, p[(c & 0xf0) >> 4]);
        ret.append(1, p[c & 0xf]);
        return ret;
    } else {
        char buf[65] = { 0 };
        (void)std::to_chars(buf, buf + sizeof(buf), c, radix);
        return QByteArray(buf);
    }
}

bool Converter::IsNewLineChar(char c, NewLineType newLineType)
{
    switch (newLineType) {
        case NEW_LINE_CR:
            return c == '\r';
        case NEW_LINE_LF:
            return c == '\n';
        case NEW_LINE_CR_LF:
            return c == '\r' || c == '\n';
        default:
            break;
    }
    return false;
}

QByteArray Converter::AsciiToIntStr(const QByteArray& txt, int radix, const ConvertSetting& cfg)
{
    QByteArray ret;
    size_t lineLen = 0;
    for (uchar c : txt) {
        if (radix == 16 && (c == '\r' || c == '\n')) {
            switch (cfg.toHexNewlineCvtMethod) {
                case NEWLINE_CVT_CVT:
                    ret.append(CharToIntStr(c, radix, cfg.uppercase));
                    break;
                case NEWLINE_CVT_TRANS:
                    ret.append(c);
                    lineLen = 0;
                    continue;
                case NEWLINE_CVT_SKIP: // fallthrough
                default:
                    continue;
            }
        } else {
            ret.append(CharToIntStr(c, radix, cfg.uppercase));
        }
        if (cfg.insertSpace) {
            ret.append(1, ' ');
        }
        lineLen++;
        if (cfg.charsPerLine <= 0 || !cfg.wholeTextConvert) {
            continue;
        }
        if (lineLen % cfg.charsPerLine == 0) {
            if (cfg.insertSpace) {
                ret.remove(ret.size() - 1, 1);
            }
            ret.append(1, '\n');
            lineLen = 0;
        }
    }
    return ret;
}

QByteArray Converter::HexToAscii(const QByteArray& txt, std::string& convertState, const ConvertSetting& cfg)
{
    QByteArray ret;
    int i = 0;
    bool success = true;
    for (; i < txt.size();) {
        uchar chigh = tolower(txt[i]);
        if (chigh == ' ') {
            ++i;
            continue;
        } else if (chigh == '\r' || chigh == '\n') {
            if (cfg.toAsciiNewlineCvtMethod != NEWLINE_CVT_SKIP) {
                ret.append(1, chigh);
            }
            ++i;
            continue;
        }
        if (!HexToChar(chigh)) {
            success = false;
            break;
        }
        if (i + 1 >= txt.size()) {
            success = false;
            break;
        }
        ++i;
        uchar clow = tolower(txt[i]);
        if (!HexToChar(clow)) {
            success = false;
            break;
        }
        uchar c = ((chigh << 4) | clow);
        if (!cfg.toAsciiSkipOutputNewline || (c != '\r' && c != '\n')) {
            ret.append(1, c);
        }
        ++i;
    }
    if (!success) {
        convertState = "invalid char '";
        convertState = convertState + char(txt[i]) + "' at position " + std::to_string(i) + ".";
        return txt;
    }
    return ret;
}
